﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Pixysoft.Framework.Noebe.Command;
using System.Data;
using Pixysoft.Tools;
using Pixysoft.Collections;

namespace Pixysoft.Framework.Noebe.Core
{
    class TransactionConnectionPool
    {
        private readonly ReaderWriterLock locker = new ReaderWriterLock();

        private ConnectionPoolPerformance performance = new ConnectionPoolPerformance();

        //available queue, NO transaction
        private ListQueue<IProxyConnection> availableQueue = new ListQueue<IProxyConnection>();
        // working queue, IN transaction
        private List<IProxyConnection> pendingQueue = new List<IProxyConnection>();

        ICommandGenerator generator = null;

        NoebeConfiguration config = null;

        public TransactionConnectionPool(NoebeConfiguration config, ICommandGenerator generator)
        {
            this.config = config;

            this.generator = generator;
        }


        public IDbConnection GetConnection(string connStr)
        {
            DoRequest();

            // if connection pool is disabled, return new one.

            if (!config.IsConnectionPoolEnabled)
            {
                return generator.GetConnection(connStr);
            }

            IProxyConnection proxyConnection = null;

            //if connection is available, reuse it.

            if (availableQueue.Count > 0)
            {
                locker.AcquireWriterLock(Timeout.Infinite);

                try
                {
                    // double-check lock

                    LoggerHelper.Debug("TRANSACTION::available pool has available connection, try to reuse it.");

                    if (availableQueue.Count > 0)
                    {
                        do
                        {
                            proxyConnection = this.GetNextAvailableConnection();

                            if (proxyConnection == null)
                            {
                                LoggerHelper.Debug("TRANSACTION::no available connection in pool, break from loop.");

                                break;
                            }

                            if (!proxyConnection.TryDoRequest())
                            {
                                LoggerHelper.Info("TRANSACTION::next connection is disposed. remove it and loop again.");

                                continue;
                            }

                            break;
                        }
                        while (availableQueue.Count > 0);

                        if (proxyConnection != null)
                        {
                            LoggerHelper.Debug("TRANSACTION::connection pool has transaction connection. reuse it, id = " + proxyConnection.ConnectionId);

                            return proxyConnection.GetConnection();
                        }
                    }
                }
                finally
                {
                    locker.ReleaseWriterLock();
                }
            }

            // create new connection

            proxyConnection = this.ThreadSafeCreateProxyConnection(connStr);

            return proxyConnection.GetConnection();
        }

        private IProxyConnection GetNextAvailableConnection()
        {
            IProxyConnection conn = this.availableQueue.Dequeue();

            pendingQueue.Add(conn);

            return conn;
        }

        private IProxyConnection CreateProxyConnection(string connStr)
        {
            IDbConnection realConnection = generator.GetConnection(connStr);

            IProxyConnection proxyConnection = new TransactionProxyConnection(this.performance.NextConnectionId().ToString(), realConnection, this);

            proxyConnection.TryDoRequest();

            pendingQueue.Add(proxyConnection);

            LoggerHelper.Debug("TRANSACTION::create new connection and add to pending queue, id = " + proxyConnection.ConnectionId);

            return proxyConnection;
        }

        private IProxyConnection ThreadSafeCreateProxyConnection(string connStr)
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                return CreateProxyConnection(connStr);
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        private void DoRequest()
        {
            locker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                this.performance.IncreaseTotalRequestTimes();
            }
            finally
            {
                locker.ReleaseReaderLock();
            }
        }

        public void PutAvailableConnection(IProxyConnection conn)
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                availableQueue.Enqueue(conn);

                LoggerHelper.Debug("TRANSACTION::pub available connection to list, id = " + conn.ConnectionId);
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        public void ThreadSafeRemoveAvailableConnection(IProxyConnection conn)
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                this.performance.IncreaseTotalRemovedTimes();

                this.performance.AddTotalRemovedRequestTimes(conn.GetRequestTimes());

                LoggerHelper.Info("TRANSACTION::drop available connection and would not append to available list, id = " + conn.ConnectionId);
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        public void RemovePendingConnection(IProxyConnection conn)
        {
            LoggerHelper.Debug("TRANSACTION::remove pending connection from list, id = " + conn.ConnectionId);

            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                pendingQueue.Remove(conn);
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        public bool TryIsOverAvailable(double requestInterval, double processInterval)
        {
            return this.pendingQueue.Count > processInterval / requestInterval;
        }

        public bool IsOverAvailable(double requestInterval, double processInterval)
        {
            if (TryIsOverAvailable(requestInterval, processInterval))
                return false;

            DateTime currentLazytime = GlobalTimer.Instance.GetGlobalTime();

            if (this.performance.GetLastLazytime() == DateTime.MinValue)
            {
                this.performance.SetLastLazytime(currentLazytime);

                return false;
            }

            if (this.performance.GetLastLazytime().AddMilliseconds(StringHelper.interval_lazytime) > currentLazytime)
            {
                return false;
            }

            LoggerHelper.Debug("TRANSACTION::connection is over available, last_lazytime = " + this.performance.GetLastLazytime() + "; processInterval = "
                    + processInterval + "; requestInterval = " + requestInterval);

            this.performance.SetLastLazytime(currentLazytime);

            return true;
        }

        public void Purge()
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                List<IProxyConnection> list = new List<IProxyConnection>();

                foreach (IProxyConnection conn in availableQueue.ToArray())
                {
                    if (!conn.IsTimeout())
                        continue;

                    availableQueue.Remove(conn);

                    list.Add(conn);
                }

                foreach (IProxyConnection conn in list)
                {
                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }

                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }

                list.Clear();

                foreach (IProxyConnection conn in pendingQueue)
                {
                    if (!conn.IsTimeout())
                        continue;

                    list.Add(conn);
                }

                foreach (IProxyConnection conn in list)
                {
                    pendingQueue.Remove(conn);

                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }


                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }

                list.Clear();
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        public void Shutdown()
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                while (availableQueue.Count > 0)
                {
                    IProxyConnection conn = availableQueue.Dequeue();

                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }


                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }

                availableQueue.Clear();

                foreach (IProxyConnection conn in pendingQueue)
                {

                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }


                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }

                pendingQueue.Clear();
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        public ConnectionPoolSnapshot CreateSnapshot()
        {
            ConnectionPoolSnapshot admin = new ConnectionPoolSnapshot();
            admin.SetConnectionLastLazyTime(this.performance.GetLastLazytime());
            admin.SetConnectionAvailableSize(this.availableQueue.Count);
            admin.SetConnectionNextConnectionId(this.performance.GetNextConnectionId());
            admin.SetConnectionRemovedQueueSize(this.pendingQueue.Count);
            admin.SetTotalRequestTime(this.performance.GetTotalRequestTimes());
            admin.SetConnectionRemovedCount(this.performance.GetTotalRemovedTimes());
            admin.SetRequestRemovedCount(this.performance.GetTotalRemovedRequestTimes());

            locker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                foreach (IProxyConnection conn in this.availableQueue.ToArray())
                {
                    admin.GetAvailableConnections().Add(conn.CreateShapshot());
                }

                foreach (IProxyConnection connin in this.pendingQueue.ToArray())
                {
                    admin.GetRemovedConnections().Add(connin.CreateShapshot());
                }
            }
            finally
            {
                locker.ReleaseReaderLock();
            }

            return admin;
        }
    }
}
